package com.qyer.commons.persistence;

import static com.qyer.commons.enums.SourceStatus.INITIAL;
import static com.qyer.commons.enums.SourceStatus.NOT_AVAILABLE;
import static com.qyer.commons.enums.SourceStatus.STANDBY;

import com.qyer.commons.enums.SourceStatus;
import com.qyer.commons.exception.DBException;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.SystemConfiguration;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * User: Z J Wu Date: 2015/11/18 Time: 13:56 Package: com.qyer.dsp.db
 */
public abstract class AbstractDBResourceManager implements DBResourceManager {

  private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDBResourceManager.class);

  protected SourceStatus sourceStatus;

  public SourceStatus getSourceStatus() {
    return sourceStatus;
  }

  protected HikariDataSource dataSource;

  @Override
  public HikariDataSource getDataSource() {
    return dataSource;
  }

  public void initByLocalConf(String confPath) throws Exception {
    CompositeConfiguration c = new CompositeConfiguration();
    // 添加顺序决定优先级, 越早添加的配置项优先级越高, 这样提供一个用户覆盖配置文件的机会
    c.addConfiguration(new SystemConfiguration());
    c.addConfiguration(new PropertiesConfiguration(confPath));
    setDataSource(c);
  }

  public void initBySysPropertiesAndLocalConf(String confPath) throws Exception {
    CompositeConfiguration c = new CompositeConfiguration();
    c.addConfiguration(new PropertiesConfiguration(confPath));
    setDataSource(c);
  }

  public synchronized void setDataSource(CompositeConfiguration c) throws Exception {
    this.sourceStatus = INITIAL;
    try {
      this.dataSource = buildHikariFromConfiguration(c);
      this.sourceStatus = STANDBY;
      LOGGER.info("Data sources have been inited({}).", dataSource);
    } catch (Exception e) {
      this.sourceStatus = NOT_AVAILABLE;
      throw e;
    }
  }

  protected HikariDataSource buildHikariFromConfiguration(CompositeConfiguration c) throws
    Exception {
    if (c == null || c.isEmpty()) {
      throw new DBException("Configuration is empty.");
    }
    String driverClass = c.getString("driver.class");
    String url = c.getString("db.url");
    String dbName = c.getString("db.dbName");
    String user = c.getString("db.user");
    String password = c.getString("db.password");
    String poolName = c.getString("pool.name");
    boolean cachePrepStmts = c.getBoolean("pool.cachePrepStmts");
    int prepStmtCacheSize = c.getInt("pool.prepStmtCacheSize");
    int prepStmtCacheSqlLimit = c.getInt("pool.prepStmtCacheSqlLimit");
    int maxPoolSize = c.getInt("pool.maxPoolSize", 10);
    boolean useServerPrepStmts = c.getBoolean("pool.useServerPrepStmts");
    return buildHikari(driverClass, url, dbName, user, password, poolName, cachePrepStmts,
                       prepStmtCacheSize, prepStmtCacheSqlLimit, maxPoolSize, useServerPrepStmts);
  }

  private HikariDataSource buildHikari(String driverClass, String url, String dbName, String user,
                                       String password, String poolName, boolean cachePrepStmts,
                                       int prepStmtCacheSize, int prepStmtCacheSqlLimit,
                                       int poolSize, boolean useServerPrepStmts) throws
    ClassNotFoundException {
    Class.forName(driverClass);
    HikariConfig config = new HikariConfig();
    config.setPoolName(poolName);
    String finalUrl = url;
    if (StringUtils.isNotEmpty(dbName)) {
      finalUrl += "/" + dbName;
      config.setJdbcUrl(url);
    }
    config.setJdbcUrl(finalUrl);
    config.setUsername(user);
    config.setPassword(password);
    config.setMaximumPoolSize(poolSize);
    config.addDataSourceProperty("cachePrepStmts", cachePrepStmts);
    config.addDataSourceProperty("prepStmtCacheSize", prepStmtCacheSize);
    config.addDataSourceProperty("prepStmtCacheSqlLimit", prepStmtCacheSqlLimit);
    config.addDataSourceProperty("useServerPrepStmts", useServerPrepStmts);
    LOGGER.info("[DB-INFO] - {}@{}", user, finalUrl);
    LOGGER.info("[DB-INFO] - Pool size - {}", poolSize);
    LOGGER.info("[DB-INFO] - Cache PrepareStatement - {}", cachePrepStmts);
    LOGGER.info("[DB-INFO] - PrepareStatement cache size(if have) - {}", prepStmtCacheSize);
    LOGGER.info("[DB-INFO] - PrepareStatement cache limit - {}", prepStmtCacheSqlLimit);
    return new HikariDataSource(config);
  }

  public long heartbeat() throws DBException {
    if (sourceStatus == null || !sourceStatus.isStandby()) {
      throw new DBException("Current db resource manager is not standby.");
    }
    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    String sql = "SELECT ?";
    DataSource ds = getDataSource();
    try {
      conn = ds.getConnection();
      pstmt = conn.prepareStatement(sql);
      pstmt.setLong(1, System.currentTimeMillis());
      rs = pstmt.executeQuery();
      if (rs.next()) {
        return rs.getLong(1);
      }
    } catch (SQLException e) {
      throw new DBException("Heartbeat got something wrong.", e);
    } finally {
      DbUtils.closeQuietly(conn, pstmt, rs);
    }
    throw new DBException("Heartbeat got something wrong.");
  }

  public void destroy() {
    if (dataSource != null) {
      dataSource.close();
    }
    LOGGER.info("Data sources have been destroyed.");
  }
}
